package com.github.aloxc.plugin.restplus.method.action;

import com.github.aloxc.plugin.DonotGuard;
import com.github.aloxc.plugin.restplus.action.AbstractBaseAction;
import com.github.aloxc.plugin.restplus.common.PsiClassHelper;
import com.github.aloxc.plugin.restplus.common.PsiMethodHelper;
import com.github.aloxc.plugin.restplus.component.ServiceNode;
import com.github.aloxc.plugin.restplus.entity.RestConfig;
import com.github.aloxc.plugin.restplus.entity.UserCase;
import com.github.aloxc.plugin.restplus.i18n.I18Bundle;
import com.github.aloxc.plugin.restplus.listener.RefreshListener;
import com.github.aloxc.plugin.restplus.navigation.action.RestServiceItem;
import com.github.aloxc.plugin.restplus.navigator.RestServiceDetail;
import com.github.aloxc.plugin.restplus.navigator.RestServicesNavigator;
import com.github.aloxc.plugin.restplus.popup.PopupBalloon;
import com.github.aloxc.plugin.restplus.resolver.*;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.CommonDataKeys;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.popup.Balloon;
import com.intellij.openapi.ui.popup.JBPopupFactory;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.wm.ex.ToolWindowEx;
import com.intellij.openapi.wm.ex.ToolWindowManagerEx;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiMethod;
import com.intellij.ui.awt.RelativePoint;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.SystemIndependent;
import org.jetbrains.kotlin.asJava.LightClassUtilsKt;
import org.jetbrains.kotlin.psi.KtNamedFunction;

import javax.swing.*;
import javax.swing.event.HyperlinkEvent;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;
import java.util.*;
import java.util.stream.Collectors;

import static com.github.aloxc.plugin.restplus.navigator.RestServicesNavigator.TOOL_WINDOW_ID;
import static com.intellij.openapi.actionSystem.CommonDataKeys.PSI_ELEMENT;
import static com.intellij.openapi.actionSystem.CommonDataKeys.PSI_FILE;

/**
 * @author leero
 */
@DonotGuard
public class RunAction extends AbstractBaseAction {


    private Editor myEditor;
    private RelativePoint point;

    public void setEditor(Editor myEditor) {
        this.myEditor = myEditor;
    }
    public void setTipsPoint(RelativePoint point) {
        this.point = point;
    }
    @Override
    public void actionPerformed(@NotNull AnActionEvent e) {
        myEditor = e.getData(CommonDataKeys.EDITOR);
        PsiElement psiElement = e.getData(PSI_ELEMENT);
        PsiFile psiFile = (PsiFile) e.getData(PSI_FILE);
        PsiMethod psiMethod = null;
        if (psiElement instanceof PsiMethod) {
            psiMethod = (PsiMethod) psiElement;
        } else if (psiElement instanceof KtNamedFunction) {
            psiMethod = LightClassUtilsKt.toLightMethods(psiElement).get(0);
        }
        Project project = e.getProject();
        @SystemIndependent final String projectPath = project.getBasePath();
        final String filePath = psiFile.getVirtualFile().getPath();
        runIt(project,psiMethod,psiFile.getVirtualFile());
    }

    public void runIt(Project project, PsiMethod psiMethod, VirtualFile virtualFile){
        Module[] modules = ModuleManager.getInstance(project).getModules();
        List<Module> curModuleList = Arrays.stream(modules).filter(module -> {
            String modulePath = module.getModuleFile().getParent().getPath();
            return virtualFile.getPath().contains(modulePath);
        }).sorted((m1, m2) ->
                m2.getModuleFile().getParent().getPath().length() - m1.getModuleFile().getParent().getPath().length()
        ).limit(1).collect(Collectors.toList());
        Module module = curModuleList.get(0);
        String servicePath = PsiMethodHelper.create(psiMethod).buildServiceUriPath();
        RestServicesNavigator servicesNavigator = project.getComponent(RestServicesNavigator.class);
        JTree urlTree = servicesNavigator.getUrlTree();
        int rootCount = ((DefaultMutableTreeNode) urlTree.getModel().getRoot()).getChildCount();
        if (rootCount == 0) {
            //还没有初始化
            final ToolWindowManagerEx manager = ToolWindowManagerEx.getInstanceEx(project);
            ToolWindowEx myToolWindow = (ToolWindowEx) manager.getToolWindow(TOOL_WINDOW_ID);
            if (myToolWindow == null) {
                return;
            }
            myToolWindow.show(() -> {
            });
            new RefreshListener(project).doRefreshProject();
            try {
                Thread.sleep(1000);
            } catch (InterruptedException ex) {
                ex.printStackTrace();
            }
            if(point == null ) {
                PopupBalloon.showPopupBalloon(myEditor, I18Bundle.getMessage("com.github.aloxc.plugin.restplus.EditorPopupMenu.run.usercase.noinit.text"), false);
            }else{
                PopupBalloon.showPopupBalloon(point, I18Bundle.getMessage("com.github.aloxc.plugin.restplus.EditorPopupMenu.run.usercase.noinit.text"), false);
            }
            return;
        }


        SpringResolver springResolver = new SpringResolver(module);
        JaxrsResolver jaxrsResolver = new JaxrsResolver(module);
        MicronautResolver micronautResolver = new MicronautResolver(module);
        BaseServiceResolver[] resolvers = {springResolver, jaxrsResolver, micronautResolver};
        List<RestServiceItem> itemList = new ArrayList<>();
        PsiClass psiClass = (PsiClass) psiMethod.getParent();
        for (BaseServiceResolver resolver : resolvers) {
            itemList.addAll(resolver.getServiceItemList(psiClass));
        }
        if (itemList.size() == 0) {
            return;
        }
        final PsiMethod psiMethodx = psiMethod;
        RestServiceItem curItem = itemList.stream().filter(item -> {
            if (item.getPsiMethod().getName().equals(psiMethodx.getName())) {
                return true;
            }
            return false;
        }).findFirst().orElse(null);


        TreePath treePath = findUrlTreePath((DefaultMutableTreeNode) urlTree.getModel().getRoot(), servicePath, curItem);
        if (treePath != null) {
            expandAll(urlTree, new TreePath(urlTree.getModel().getRoot()), true);
            urlTree.setSelectionPath(treePath);
            urlTree.scrollPathToVisible(treePath);
        }

        RestConfig config = RestConfig.loadConfig(project);
        Map<String, UserCase> userCaseMap = config.getUserCaseMap().get(servicePath);
        if (userCaseMap == null || userCaseMap.size() == 0) {
            if(point == null) {
                PopupBalloon.showPopupBalloon(myEditor, I18Bundle.getMessage("com.github.aloxc.plugin.restplus.EditorPopupMenu.run.no.usercase"), false);
            }else{
                PopupBalloon.showPopupBalloon(point, I18Bundle.getMessage("com.github.aloxc.plugin.restplus.EditorPopupMenu.run.no.usercase"), false);
            }
        } else {
            StringBuffer html = new StringBuffer("<ul>");
            StringBuffer c = new StringBuffer();
            userCaseMap.entrySet().stream().forEach(k -> {
                String name = UserCase.getUserCaseName(k.getKey());
                String m = UserCase.getUserCaseMethod(k.getKey());
                if (m.equals(curItem.getMethod().name())) {
                    c.append("<li><a href=\"" + name + "\">")
                            .append(name)
                            .append("</a></li>");
                }
            });
            html.append(c);
            html.append("</ul>");
            if (c.length() == 0) {
                if(point == null) {
                    PopupBalloon.showPopupBalloon(myEditor, I18Bundle.getMessage("com.github.aloxc.plugin.restplus.EditorPopupMenu.run.no.usercase"), false);
                }else{
                    PopupBalloon.showPopupBalloon(point, I18Bundle.getMessage("com.github.aloxc.plugin.restplus.EditorPopupMenu.run.no.usercase"), false);
                }
            } else {
                showPopupBalloon(myEditor, html.toString(), servicesNavigator);
            }
        }
    }

    private void expandAll(JTree tree, TreePath parent, boolean expand) {
        TreeNode node = (TreeNode) parent.getLastPathComponent();
        if (node.getChildCount() > 0) {
            for (Enumeration e = node.children(); e.hasMoreElements(); ) {
                TreeNode n = (TreeNode) e.nextElement();
                TreePath path = parent.pathByAddingChild(n);
                expandAll(tree, path, expand);

            }
        }
        if (expand) {
            tree.expandPath(parent);
        } else {
            tree.collapsePath(parent);
        }
    }

    private TreePath findUrlTreePath(DefaultMutableTreeNode root, String s, RestServiceItem restServiceItem) {
        @SuppressWarnings("unchecked")
        Enumeration<TreeNode> e = root.depthFirstEnumeration();
        while (e.hasMoreElements()) {
            DefaultMutableTreeNode node = (DefaultMutableTreeNode) e.nextElement();
            if (node.toString().equalsIgnoreCase(s)) {
                if (node instanceof ServiceNode) {
                    ServiceNode serviceNode = (ServiceNode) node;
                    if (serviceNode.getRestServiceItem().getMethod().name().equals(restServiceItem.getMethod().name())) {
                        return new TreePath(node.getPath());
                    }
                }
            }
        }
        return null;
    }

    private TreePath findUserCaseTreePath(DefaultMutableTreeNode root, String s) {
        @SuppressWarnings("unchecked")
        Enumeration<TreeNode> e = root.depthFirstEnumeration();
        while (e.hasMoreElements()) {
            DefaultMutableTreeNode node = (DefaultMutableTreeNode) e.nextElement();
            if (node.toString().equalsIgnoreCase(s)) {
                return new TreePath(node.getPath());
            }
        }
        return null;
    }

    @Override
    public void update(@NotNull AnActionEvent e) {
        super.update(e);
        PsiElement psiElement = e.getData(CommonDataKeys.PSI_ELEMENT);
        boolean visible = false;
        if (psiElement instanceof PsiMethod) {
            PsiMethod psiMethod = (PsiMethod) psiElement;
            // rest method 或标注了RestController 注解
            visible = (PsiClassHelper.isRestController(psiMethod.getContainingClass()) && PsiMethodHelper.isRestfulMethod(psiMethod));
        }
        if (psiElement instanceof KtNamedFunction) {//判断是否是kotlin方法
            KtNamedFunction fun = (KtNamedFunction) psiElement;
            List<PsiMethod> psiMethods = LightClassUtilsKt.toLightMethods(psiElement);
            for (PsiMethod psiMethod : psiMethods) {
                visible = (PsiClassHelper.isRestController(psiMethod.getContainingClass()) && PsiMethodHelper.isRestfulMethod(psiMethod));
            }
        }
        if (visible) {
            e.getPresentation().setText(I18Bundle.getMessage("com.github.aloxc.plugin.restplus.EditorPopupMenu.run.text"));
            e.getPresentation().setDescription(I18Bundle.getMessage("com.github.aloxc.plugin.restplus.EditorPopupMenu.run.text"));
        }

        setActionPresentationVisible(e, visible);
    }

    public void showPopupBalloon(final Editor editor, final String result, final RestServicesNavigator servicesNavigator) {
        ApplicationManager.getApplication().invokeLater(new Runnable() {
            @Override
            public void run() {
                JBPopupFactory factory = JBPopupFactory.getInstance();
                factory.createHtmlTextBalloonBuilder(result, null, PopupBalloon.NORMAL,
                        e -> {
                            if (e.getEventType() != HyperlinkEvent.EventType.ACTIVATED) {
                                return;
                            }
                            String userCaseName = e.getDescription();
                            RestServiceDetail restServiceDetail = servicesNavigator.getRestServiceStructure().getRestServiceDetail();
                            JTree userCaseTree = restServiceDetail.getUserCaseTree();
                            TreePath treePath = findUserCaseTreePath((DefaultMutableTreeNode) userCaseTree.getModel().getRoot(), userCaseName);
                            userCaseTree.setSelectionPath(treePath);
                            restServiceDetail.getSendActionListener().send(editor,point);
                        })
                        .setTitle(I18Bundle.getMessage("com.github.aloxc.plugin.restplus.EditorPopupMenu.run.usercase.select.text"))
                        .setHideOnLinkClick(true)
                        .setFadeoutTime(15000)
                        .createBalloon()
                        .show(point == null ? factory.guessBestPopupLocation(editor) : point, Balloon.Position.above);
            }
        });
    }
}
